package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.article.mapper.*;
import com.heima.article.service.ApArticleService;
import com.heima.feigns.behavior.BehaviorFeign;
import com.heima.feigns.user.UserFeign;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.dtos.ArticleInfoDto;
import com.heima.model.article.pojos.*;
import com.heima.model.article.vos.HotArticleVo;
import com.heima.model.behavior.dtos.ApArticleRelationDto;
import com.heima.model.common.constants.ArticleConstants;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.mess.app.ArticleVisitStreamMess;
import com.heima.model.user.pojos.ApUser;
import com.heima.model.user.pojos.ApUserFollow;
import com.heima.utils.threadlocal.AppThreadLocalUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

@Service
@Transactional
@Slf4j
public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {

    @Autowired
    AuthorMapper authorMapper;
    @Autowired
    ApArticleConfigMapper apArticleConfigMapper;
    @Autowired
    ApArticleContentMapper apArticleContentMapper;
    @Value("file.oss.web-site")
    String webSite;

    @Autowired
    private BehaviorFeign behaviorFeign;
    @Autowired
    private UserFeign userFeign;
    @Autowired
    ApCollectionMapper apCollectionMapper;
    @Autowired
    KafkaTemplate kafkaTemplate;
    @Autowired
    RedisTemplate redisTemplate;

    /**
     * 重新计算文章分值  实时计算
     * 1.更新数据库article表中信息
     *2.更新缓存中数据
     * @param mess
     */
    @Override
    public void updateApArticle(ArticleVisitStreamMess mess) {
        //边界条件---文章必须存在
        log.info("重新计算文章分值*****************updateApArticle is begin: {}",mess);
        //根据文章id查询文章
        ApArticle article = getById(mess.getArticleId());
        if (article==null){
            log.info("**********此id文章不存在:{} ",mess.getArticleId());
            return;
        }
        //根据查询结果，以及kafka接收到的结果更新点赞，收藏数量
        if (mess.getView() != 0) {
            article.setViews((int) (article.getViews() == null ? mess.getView() : mess.getView() + article.getViews()));
        }
        if (mess.getLike() != 0) {
            int like = (int) (article.getLikes() == null ? mess.getLike() : mess.getLike() + article.getLikes());
            article.setLikes(like);
        }
        if (mess.getComment() != 0) {
            int comment = (int) (article.getComment() == null ? mess.getComment() : mess.getComment() + article.getComment());
            article.setComment(comment);
        }
        if (mess.getCollect() != 0) {
            int collection = (int) (article.getCollection() == null ? mess.getCollect() : mess.getCollect() + article.getCollection());
            article.setCollection(collection);
        }

        //更新数据库文章表数据
        updateById(article);

        //重新计算分数***当日分数*3
        Integer score = computeScore(article)*3;
        /**
         * 更新缓存数据，******先查询再更新
         * 1.根据频道id更新数据
         * 2.推荐页更新数据
        */
        String tag=ArticleConstants.HOT_ARTICLE_FIRST_PAGE + article.getChannelId();
        String hotArticlesStr =(String) redisTemplate.opsForValue().get(tag);
        updateArticleCache(article,score,hotArticlesStr,tag);

        String tagAll=ArticleConstants.HOT_ARTICLE_FIRST_PAGE+ArticleConstants.DEFAULT_TAG;
        String allHotArticleStr= (String) redisTemplate.opsForValue().get(tagAll);
        updateArticleCache(article,score,allHotArticleStr, tagAll);

    }

    /**
     * 根据用户行为更新缓存中热点数据
     * @param article
     * @param score
     * @param hotArticlesStr
     * @param tag
     */
    private void updateArticleCache(ApArticle article, Integer score, String hotArticlesStr, String tag) {
        //查询到的热点字符串不能为空
        if (StringUtils.isNotBlank(hotArticlesStr)){
            List<HotArticleVo> hotArticleVos = JSON.parseArray(hotArticlesStr, HotArticleVo.class);
            boolean flag=false;

            //如果缓存中有当前文章数据则更新分数,排序存入缓存
            for (HotArticleVo hotArticleVo : hotArticleVos) {
                if (hotArticleVo.getId().equals(article.getId())){
                    hotArticleVo.setScore(score);
                    flag=true;
                    break;
                }
            }
            if (flag){//如果缓存中存在
                List<HotArticleVo> articleVos= hotArticleVos.stream()
                        .sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                        .collect(Collectors.toList());
                redisTemplate.opsForValue().set(tag,JSON.toJSONString(articleVos));
            }else {//如果不存在数据则先插入数据,排序并获取前30条，，存入缓存
                HotArticleVo hotArticleVo=new HotArticleVo();
                BeanUtils.copyProperties(article,hotArticleVo);
                hotArticleVo.setScore(score);
                hotArticleVos.add(hotArticleVo);
                List<HotArticleVo> articleVos= hotArticleVos.stream()
                        .sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                        .limit(30)
                        .collect(Collectors.toList());
                redisTemplate.opsForValue().set(tag,JSON.toJSONString(articleVos));
            }

        }
    }

    /**
     * 2.1计算文章分值算法
     * @param apArticle
     * @return
     */
    private Integer computeScore(ApArticle apArticle) {
        int score = 0;
        // 阅读 1
        if (apArticle.getViews() != null) {
            score += apArticle.getViews() * ArticleConstants.HOT_ARTICLE_VIEW_WEIGHT;
        }
        // 点赞 3
        if (apArticle.getLikes() != null) {
            score += apArticle.getLikes() * ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT;
        }
        // 评论 5
        if (apArticle.getComment() != null) {
            score += apArticle.getComment() * ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT;
        }
        // 收藏 8
        if (apArticle.getCollection() != null) {
            score += apArticle.getCollection() * ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT;
        }

        return score;
    }

    /**
     * 保存或修改文章
     *
     * @param dto
     * @return
     */
    @Override
    public ResponseResult saveArticle(ArticleDto dto) {
        //参数非空检查
        if (dto==null){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        //保存数据到 article表  并回填articleId
        ApArticle apArticle=new ApArticle();
        BeanUtils.copyProperties(dto,apArticle);
        //传送dto时遗留问题，不想远程调用查找设置作者id，减少远程调用次数
        ApAuthor apAuthor = authorMapper.selectOne(
                Wrappers.<ApAuthor>lambdaQuery()
                        .eq(ApAuthor::getName, apArticle.getAuthorName())
        );
        if (apAuthor==null){
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
        }
        apArticle.setAuthorId(apAuthor.getId().longValue());
        //根据dto的文章id 是否有值来判断发布还是修改==================将数据保存在三个表中
        if (dto.getId()==null){
            //article表
            apArticle.setComment(0);
            apArticle.setViews(0);
            apArticle.setCollection(0);
            apArticle.setLikes(0);

            save(apArticle);//此时会有文章id回填
            //articleConfig表
            ApArticleConfig apArticleConfig=new ApArticleConfig();
            apArticleConfig.setIsForward(true);
            apArticleConfig.setIsComment(true);
            apArticleConfig.setIsDown(false);
            apArticleConfig.setIsDelete(false);
            apArticleConfig.setArticleId(apArticle.getId());

            apArticleConfigMapper.insert(apArticleConfig);
            //articleContent表
            ApArticleContent apArticleContent=new ApArticleContent();
            apArticleContent.setContent(dto.getContent());
            apArticleContent.setArticleId(apArticle.getId());

            apArticleContentMapper.insert(apArticleContent);
        }else {
            //article表******* 先查找再更新
            ApArticle article = getById(apArticle.getId());
            if (article==null){
                log.info("ApArticle getById is null: {}", dto.getId());
                return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
            }

            updateById(apArticle);
            //articleConfig表 ,无需修改
            //articleContent表******* 先查找再更新
            ApArticleContent apArticleContent = apArticleContentMapper.selectOne(
                    Wrappers.<ApArticleContent>lambdaQuery()
                            .eq(ApArticleContent::getArticleId, dto.getId())
            );
            if (apArticleContent==null){
                log.info("articleContent getById is null: {}",dto.getId());
                return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
            }
            apArticleContent.setContent(dto.getContent());

            apArticleContentMapper.updateById(apArticleContent);
        }

        //卡夫卡发消息到索引库
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("id", apArticle.getId().toString());
        map.put("publishTime", apArticle.getPublishTime());
        map.put("layout", apArticle.getLayout());
        map.put("images", apArticle.getImages());
        map.put("authorId", apArticle.getAuthorId());
        map.put("title", apArticle.getTitle());
        kafkaTemplate.send(ArticleConstants.ARTICLE_ES_SYNC_TOPIC, JSON.toJSONString(map));

        return ResponseResult.okResult(apArticle.getId());
    }

    /**
     * 根据参数加载文章列表
     *
     * @param dto
     * @param loadtype
     * @return
     */
    @Override
    public ResponseResult load(ArticleHomeDto dto, Short loadtype) {
        if (dto==null){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        Integer size=dto.getSize();
        if (size==null ||size<=0){
            size=10;
        }
        size= Math.min(size, 50);
        dto.setSize(size);
        if (StringUtils.isEmpty(dto.getTag())){
            dto.setTag(ArticleConstants.DEFAULT_TAG);
        }
        if (dto.getMaxBehotTime()==null){
            dto.setMaxBehotTime(new Date());
        }
        if (dto.getMinBehotTime()==null){
            dto.setMinBehotTime(new Date());
        }
        if (!(loadtype.equals(ArticleConstants.LOADTYPE_LOAD_MORE) && loadtype.equals(ArticleConstants.LOADTYPE_LOAD_NEW))){
            loadtype=ArticleConstants.LOADTYPE_LOAD_MORE;
        }

        List<ApArticle> list=baseMapper.loadArticleList(dto, loadtype);

        //封装结果时记得加图片前缀
        ResponseResult result = ResponseResult.okResult(list);
        result.setHost(webSite);

        return result;
    }

    /**
     * 根据参数加载文章列表  v2
     *
     * @param loadtype
     * @param dto
     * @param firstPage
     * @return
     */
    @Override
    public ResponseResult load2(Short loadtype, ArticleHomeDto dto, boolean firstPage) {
        //如果是首页在redis中加载，不是首页，则调用load方法去数据库加载
        if (firstPage){
            String strList = (String) redisTemplate.opsForValue()
                    .get(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + dto.getTag());
            if (StringUtils.isNotBlank(strList)){
                List<HotArticleVo> hotArticleVos = JSON.parseArray(strList, HotArticleVo.class);
                if (!hotArticleVos.isEmpty()&& hotArticleVos.size()>0){
                    ResponseResult result = ResponseResult.okResult(hotArticleVos);
                    result.setHost(webSite);
                    return result;
                }
            }
        }
        return load(dto,loadtype);
    }

    /**
     * 加载文章详情
     *
     * @param dto
     * @return
     */
    @Override
    public ResponseResult loadArtilceInfo(ArticleInfoDto dto) {
        if (dto==null || dto.getArticleId()==null){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        //去config表查询是否下架或删除
        ApArticleConfig articleConfig = apArticleConfigMapper.selectOne(
                Wrappers.<ApArticleConfig>lambdaQuery()
                        .eq(ApArticleConfig::getArticleId, dto.getArticleId())
        );
        if (articleConfig==null){
            return ResponseResult.errorResult(AppHttpCodeEnum.AP_USER_DATA_NOT_EXIST);
        }
        if (articleConfig.getIsDelete() || articleConfig.getIsDown()){
            return ResponseResult.errorResult(AppHttpCodeEnum.AP_USER_DATA_NOT_EXIST);
        }
        //文章存在时，去articleContent表查询文章
        ApArticleContent content = apArticleContentMapper.selectOne(
                Wrappers.<ApArticleContent>lambdaQuery()
                        .eq(ApArticleContent::getArticleId, dto.getArticleId())
        );
        if(content==null){
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
        }
        Map map=new HashMap();
        map.put("config",articleConfig);
        map.put("content",content);
        ResponseResult result = ResponseResult.okResult(map);
        result.setHost(webSite);
        return result;
    }

    /**
     * 加载文章详情的初始化配置信息，比如关注、喜欢、不喜欢等
     *
     * @param dto
     * @return
     */
    @Override
    public ResponseResult loadArticleBehavior(ArticleInfoDto dto) {
        if (dto==null || dto.getArticleId()==null ||dto.getAuthorId()==null){
            return  ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        boolean iscollection=false,isfollow=false;
        ApUser user = AppThreadLocalUtils.getUser();
        //是否关注
        ApAuthor apAuthor = authorMapper.selectById(dto.getAuthorId());
        if (apAuthor==null){
            return null;
        }
        ApUserFollow apUserFollow = userFeign.findByUserIdAndFollowId(user.getId(), apAuthor.getUserId());
        if (apUserFollow!=null) {
            isfollow = true;
        }
        //是否点赞
        Map behaviorMap = behaviorFeign.findApArticleRelation(
                ApArticleRelationDto.builder()
                        .articleId(dto.getArticleId())
                        .entryId(user.getId())
                        .type((short) 1)
                        .build()
        );
        //是否收藏
        ApCollection apCollection = apCollectionMapper.selectOne(
                Wrappers.<ApCollection>lambdaQuery()
                        .eq(ApCollection::getArticleId, dto.getArticleId())
                        .eq(ApCollection::getEntryId, behaviorMap.get("entryId"))
        );
        if (apCollection!=null){
            iscollection=true;
        }
        //封装map
        Map resultMap = new HashMap();
        resultMap.put("iscollection",iscollection);
        resultMap.put("isfollow",isfollow);
        resultMap.putAll(behaviorMap);

        return ResponseResult.okResult(resultMap);
    }
}
